#include "./header/graph.h"
#include "randomgenerator.h"
#include "stock.h"
#include "predictor.h"
#include "ui_graph.h"

const QString graph::googleStockCode        = "GOOG";
const QString graph::googleDataFile         = "/Users/pfn368/junk/mini-stock-exchange/google.dat";
const QString graph::microsoftStockCode     = "MSFT";
const QString graph::microsoftDataFile      = "/Users/pfn368/junk/mini-stock-exchange/microsoft.dat";
const QString graph::appleStockCode         = "AAPL";
const QString graph::appleDataFile          = "/Users/pfn368/junk/mini-stock-exchange/apple.dat";
/*
** This is the constructor of the grapher method, which basicaly sets the three stock codes that we process
*/
graph::graph(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::graph)
{
    ui->setupUi(this);
    ui->comboBox->addItem(googleStockCode);
    ui->comboBox->addItem(microsoftStockCode);
    ui->comboBox->addItem(appleStockCode);
    ui->checkBoxStock->setChecked(true);
    timer = new QTimer();
}
/*
** Destructor
*/
graph::~graph() {
    qDebug() << "Destructor";
    delete ui;
}

/*
** This is a setter function for setting the stock code.
*/
void graph::setStockCode(QString string) {
    stockCode = string;
}

void graph::setConnectionOfTimer() {
    qDebug() << "Prediction STOCK_MAX_PRICE = " << this->getPredictor()->getHighestStockValue();
    QObject::connect(timer, SIGNAL(timeout()), this->getPredictor(), SLOT(startPrediction()));

}

/*
** This is the virtual paintEvent method which is overridden to paint the graph from the data that is available
** It first draws the X-axis and then labels it with the time factor adding 15 mins to the next time.
** Next it draws the Y-axis adn then labels it with starting number as 0 and the ending number as 650.
** TODO: I have to take care of reszing the graph and also take care to see what should be the maximum number on Y-axis.
** Basically is draws like using the drawLine method of the painter takign the starting and the ending points.
** This method also draws the portfolio when the use checks the portFolioData checkbox.
*/
void graph::paintEvent(QPaintEvent *) {
    qDebug() << "Starting to paint";
    QPainter p(this);
    int n=0; double time[10000];
    int pR=0; double timeP[1000];
    endOfYaxis = stockData->getHighestStockReading().toFloat() + 150;
    QDateTime startTime = QDateTime::fromString("2011-12-01 09:30:00", "yyyy-MM-dd HH:mm:ss");

    //drawing X-axis line
    QPointF originx = QPointF(50, endOfYaxis); //xorigin
    QPointF xaxis  = QPointF(1350, endOfYaxis); //length of x-axis
    p.setPen(QPen(Qt::black, 1));
    p.drawLine(originx, xaxis);

    //draw X-axis lables
    int xCounter = 0;int xSpacingText = 30;
    QDateTime labelTime = QDateTime::fromString("2011-12-01 09:30:00", "yyyy-MM-dd HH:mm:ss");
    QString xlabel[500];
    p.setPen(QPen(Qt::black, 1));
    while(1) {
        xlabel[xCounter] = labelTime.toString("HH:mm");
        labelTime = labelTime.addSecs(interval);
        p.drawText(xSpacingText, endOfYaxis+20, xlabel[xCounter]);
        p.drawLine(QPointF(xSpacingText+20, endOfYaxis), QPointF(xSpacingText+20, endOfYaxis+5));
        if (labelTime.time() >= QTime::fromString("16:01:00", "hh:mm:ss"))
            break;
        xCounter++;
        xSpacingText = xSpacingText + spaceBetweenLabels;
    }

    //drawing Y-axis line
    QPointF originy = QPointF(50, startOfYaxis+50); //yorigin
    QPointF yaxis  = QPointF(50, endOfYaxis); //height of y-axis

    p.setPen(QPen(Qt::black, 1));
    p.drawLine(originy, yaxis);
    p.drawText(700, endOfYaxis+35, "Time units");

    //draw Y-axis lables
    int ySpacingText = 0;
    int noOfYLabels = 0;
    while(1) {
        noOfYLabels++;
        p.drawLine(QPointF(45, endOfYaxis - ySpacingText), QPointF(50 , endOfYaxis - ySpacingText));
        QVariant var1(ySpacingText);
        p.drawText(QPointF(20, endOfYaxis - ySpacingText + 3), var1.toString());
        ySpacingText = ySpacingText + spaceBetweenLabels;
        if (ySpacingText > (endOfYaxis - 100))
            break;
    }

    double normalizingFactor = interval / spaceBetweenLabels; //is the normalizing factor for the graph

    QHash<QString, QString> row;
    QList<QHash<QString, QString> > list;
    list = stockData->fileData;
    foreach(row, list) {
        QDateTime current = QDateTime::fromString(row.value("time"), "yyyy-MM-dd HH:mm:ss");
        int secsFromStart = (current.toMSecsSinceEpoch() - startTime.toMSecsSinceEpoch()) / 1000;
        time[n] = ( secsFromStart) / normalizingFactor;
        n++;
    }

    if (stockCode == googleStockCode) {
        p.setPen(QPen(Qt::red, 1));
    }
    else if (stockCode == appleStockCode) {
        p.setPen(QPen(Qt::darkGreen, 1));
    }
    else if (stockCode == microsoftStockCode) {
        p.setPen(QPen(Qt::blue, 1));
    }
    else {
        p.setPen(QPen(Qt::red, 1));
    }
    int layoutMarginLeft = 38;

    RegressionAnalysis *ra = new RegressionAnalysis();
    ra->setStockData(stockData);

    for (int i=0; i< stockData->fileData.size()-1 & ui->checkBoxStock->isChecked();i++) {
        QPointF startPoint = QPointF(layoutMarginLeft + time[i], endOfYaxis - stockData->fileData.at(i).value("price").toDouble());
        QPointF endPoint   = QPointF(layoutMarginLeft + time[i+1], endOfYaxis - stockData->fileData.at(i+1).value("price").toDouble());
        if (ui->checkBox->isChecked()) {
            QVariant var1(stockData->fileData.at(i).value("price"));p.drawText(startPoint, var1.toString());
        }
        p.drawLine(startPoint, endPoint);

        //Regression plotting
        QString price1 = ra->getYfromEquation(stockData->fileData.at(i).value("time"));
        QString price2 = ra->getYfromEquation(stockData->fileData.at(i+1).value("time"));
        QPointF startPointRegression = QPointF(layoutMarginLeft + time[i], endOfYaxis - price1.toDouble());
        QPointF endPointRegression   = QPointF(layoutMarginLeft + time[i+1], endOfYaxis - price2.toDouble());

        p.drawLine(startPointRegression, endPointRegression);
    }

    if (stockData->predictorData.size() > 0 & ui->checkBoxStock->isChecked()) { //Just check if there is any prediction data to plot, or else skip plotting predictor
        list = stockData->predictorData;
        foreach(row, list) {
            QDateTime current = QDateTime::fromString(row.value("time"), "yyyy-MM-dd HH:mm:ss");
            int secsFromStart = (current.toMSecsSinceEpoch() - startTime.toMSecsSinceEpoch()) / 1000;
            timeP[pR] = ( secsFromStart) / normalizingFactor;
            pR++;
        }
        if (stockCode == googleStockCode) {
            p.setPen(QPen(QColor(199,21,133), 1));
        }
        else if (stockCode == appleStockCode) {;
            p.setPen(QPen(QColor(124,252,0), 1));
        }
        else if (stockCode == microsoftStockCode) {
            p.setPen(QPen(QColor(100,149,237), 1));
        }
        else {
            p.setPen(QPen(QColor(199,21,133), 1));
        }

        for (int i=0; i< stockData->predictorData.size()-1;i++) {
            QPointF startPoint; QPointF endPoint;

            if (i == 0) {
                startPoint = QPointF(layoutMarginLeft + time[n-1], endOfYaxis - stockData->fileData.at(n-1).value("price").toDouble());
                endPoint   = QPointF(layoutMarginLeft + timeP[i], endOfYaxis - stockData->predictorData.at(i).value("price").toDouble());
                p.drawLine(startPoint, endPoint);

                QString price1 = ra->getYfromEquation(stockData->fileData.at(stockData->fileData.size()-1).value("time"));
                QString price2 = ra->getYfromEquation(stockData->predictorData.at(i).value("time"));
                QPointF startPointRegression = QPointF(layoutMarginLeft + time[n-1], endOfYaxis - price1.toDouble());
                QPointF endPointRegression   = QPointF(layoutMarginLeft + timeP[i], endOfYaxis - price2.toDouble());
                p.drawLine(startPointRegression, endPointRegression);
            }

            startPoint = QPointF(layoutMarginLeft + timeP[i], endOfYaxis - stockData->predictorData.at(i).value("price").toDouble());
            endPoint   = QPointF(layoutMarginLeft + timeP[i+1], endOfYaxis - stockData->predictorData.at(i+1).value("price").toDouble());

            if (ui->checkBox->isChecked()) {
                QVariant var1(stockData->predictorData.at(i).value("price"));p.drawText(startPoint, var1.toString());
            }

            p.drawLine(startPoint, endPoint);

            QString price1 = ra->getYfromEquation(stockData->predictorData.at(i).value("time"));
            QString price2 = ra->getYfromEquation(stockData->predictorData.at(i+1).value("time"));
            QPointF startPointRegression = QPointF(layoutMarginLeft + timeP[i], endOfYaxis - price1.toDouble());
            QPointF endPointRegression   = QPointF(layoutMarginLeft + timeP[i+1], endOfYaxis - price2.toDouble());

            p.drawLine(startPointRegression, endPointRegression);
        }
    }

    //Portfolio drawing only if the checkbox is clicked
    if (ui->checkBoxDataPortfolio->isChecked()) {
        //drawing portfoio-axis line
        QPointF originPortfolio = QPointF(1350, startOfYaxis+50); //yorigin
        QPointF yaxisPortfolio  = QPointF(1350, endOfYaxis); //height of y-axis

        p.setPen(QPen(Qt::black, 1));
        p.drawLine(originPortfolio, yaxisPortfolio);

        QList<QString>keysData = cache.keys();
        QHash<QString, QString> portfolioInitialPricing;
        for (int i=0;i<keysData.size();i++) {
            QString keyStock = keysData.at(i);
            Stock *tempStock = cache.value(keyStock);
            QHash<QString, QString> firstLineRead =  tempStock->getFirstLineRead();
            portfolioInitialPricing.insert(keyStock, firstLineRead.value("price"));
        }

        QString key;
        QHash<QString, QMap<qint64, QString> > timePriceList;
        qint32 stockValue=0;
        foreach(key, keysData) {
            Stock *tempStockData = cache.value(key);
            if (key == "GOOG") {
                if (tempStockData->predictorData.size() > 0) {
                    qDebug()<<"Comes here?";
                    qint32 size = tempStockData->predictorData.size();
                    stockValue += tempStockData->predictorData.at(size-1).value("price").toDouble()*ui->spinBoxGoogle->value();
                }
                else {
                    qDebug()<<"Comes here!";
                    stockValue += tempStockData->fileData.at(tempStockData->fileData.size()-1).value("price").toDouble()* ui->spinBoxGoogle->value();
                }

            }
            else if (key=="AAPL") {
                if (tempStockData->predictorData.size() > 0) {
                    qDebug()<<"Comes here?";
                    stockValue += tempStockData->predictorData.at(tempStockData->predictorData.size()-1).value("price").toDouble()*ui->spinBoxApple->value();
                }
                else {
                    qDebug()<<"Comes here!";
                    stockValue += tempStockData->fileData.at(tempStockData->fileData.size()-1).value("price").toDouble()* ui->spinBoxApple->value();
                }
            }
            else if (key=="MSFT") {
                if (tempStockData->predictorData.size() > 0) {
                    qDebug()<<"Comes here?";
                    stockValue += tempStockData->predictorData.at(tempStockData->predictorData.size()-1).value("price").toDouble()*ui->spinBoxMicrosoft->value();
                }
                else {
                    qDebug()<<"Comes here!";
                    stockValue += tempStockData->fileData.at(tempStockData->fileData.size()-1).value("price").toDouble()* ui->spinBoxMicrosoft->value();
                }
            }

            QMap<qint64, QString> data;
            for(int i=0;i<tempStockData->fileData.size();i++) {
                QString time = tempStockData->fileData.at(i).value("time");
                QDateTime tempTime = QDateTime::fromString(time, "yyyy-MM-dd HH:mm:ss");
                QString price = tempStockData->fileData.at(i).value("price");
                data.insert(tempTime.toMSecsSinceEpoch(), price);
            }
            for(int i=0;i<tempStockData->predictorData.size();i++) {
                QString time = tempStockData->predictorData.at(i).value("time");
                QDateTime tempTime = QDateTime::fromString(time, "yyyy-MM-dd HH:mm:ss");
                QString price = tempStockData->predictorData.at(i).value("price");
                data.insert(tempTime.toMSecsSinceEpoch(), price);
            }
            timePriceList.insert(tempStockData->fileData.at(0).value("stockCode"), data);
        }
        qint64 key1;
        QMap<qint64, QString> portfolioData;
        double lowPrice = 999999999;
        double highPrice = 0;
        foreach(key, timePriceList.keys()) {
            QMap<qint64, QString> timeList = timePriceList.value(key);
            foreach(key1, timeList.keys()) {
                QDateTime tempTime = QDateTime::fromMSecsSinceEpoch(key1);
                if (!(portfolioData.contains(tempTime.toMSecsSinceEpoch()))) {
                    portfolioData.insert(tempTime.toMSecsSinceEpoch(), "0");
                }
            }
        }

        foreach(key, timePriceList.keys()) {
            QMap<qint64, QString> tempTime1 = timePriceList.value(key);
            foreach(key1, portfolioData.keys()) {
                if (tempTime1.contains(key1)) {
                    portfolioInitialPricing.insert(key, tempTime1.value(key1));
                }
                else {
                     tempTime1.insert(key1, portfolioInitialPricing.value(key));
                }
            }
            timePriceList.insert(key, tempTime1);
        }

        foreach(key, timePriceList.keys()) {
            QMap<qint64, QString> timePriceDataStock = timePriceList.value(key);
            qint64 key1;

            foreach(key1, portfolioData.keys()) {
                double price;
                if(timePriceDataStock.contains(key1)) {
                    if (key == graph::googleStockCode) {
                        price = portfolioData.value(key1).toDouble() + timePriceDataStock.value(key1).toDouble() * ui->spinBoxGoogle->value();
                    }
                    else if (key == graph::microsoftStockCode) {
                        price = portfolioData.value(key1).toDouble() + timePriceDataStock.value(key1).toDouble() * ui->spinBoxMicrosoft->value();
                    }
                    else if (key == graph::appleStockCode) {
                        price = portfolioData.value(key1).toDouble() + timePriceDataStock.value(key1).toDouble() * ui->spinBoxApple->value();
                    }
                    portfolioData.insert(key1, QString::number(price));
                }
            }
        }
        double price;
        qint64 tempTime;
        foreach(tempTime, portfolioData.keys()) {
            price = portfolioData.value(tempTime).toDouble();
            if (price < lowPrice) {
                lowPrice = price;
            }
            if (price > highPrice) {
                highPrice = price;
            }
        }

        int priceDiff = ((int)highPrice % 50);
        double pricetoBeAdded = 0;
        if (priceDiff != 0) {
            pricetoBeAdded = (50 - priceDiff);
        }
        int incrementsPortfolio = ((int)highPrice + pricetoBeAdded) / (noOfYLabels-1);

        //draw portfoio-axis lables
        int portfolioText = 0;
        ySpacingText = 0;
        while(1) {
            p.drawLine(QPointF(1350, endOfYaxis - ySpacingText), QPointF(1355 , endOfYaxis - ySpacingText));
            QVariant var1(portfolioText);
            p.drawText(QPointF(1360, endOfYaxis - ySpacingText + 3), var1.toString());
            portfolioText = portfolioText + incrementsPortfolio;
            ySpacingText = ySpacingText + spaceBetweenLabels;
            if (ySpacingText > (endOfYaxis - 100))
                break;
        }
        double LeftLable = (ySpacingText - spaceBetweenLabels);
        double RightLable = (portfolioText - incrementsPortfolio);

        double multiplyFactor = (LeftLable/RightLable);

        double timeFirstPoint, timeSecondPoint;
        for(int i=0; i< portfolioData.size()-1;i++) {
            int secsFromStart = (portfolioData.keys().at(i) - startTime.toMSecsSinceEpoch()) / 1000;
            timeFirstPoint = ( secsFromStart) / normalizingFactor;
            secsFromStart = (portfolioData.keys().at(i+1) - startTime.toMSecsSinceEpoch()) / 1000;
            timeSecondPoint = ( secsFromStart) / normalizingFactor;
            QPointF startPoint = QPointF(layoutMarginLeft + timeFirstPoint, endOfYaxis - (portfolioData.value(portfolioData.keys().at(i)).toDouble()*multiplyFactor));
            QPointF endPoint   = QPointF(layoutMarginLeft + timeSecondPoint, endOfYaxis - (portfolioData.value(portfolioData.keys().at(i+1)).toDouble()*multiplyFactor));
            if (ui->checkBoxPortfolio->isChecked()) {
                QVariant var1(portfolioData.value(portfolioData.keys().at(i)));p.drawText(startPoint, var1.toString());
            }
            p.drawLine(startPoint, endPoint);
        }
        ui->totalCount->setText(QString::number(stockValue));
    }
    else {
        ui->totalCount->setText("0");
    }

    p.end();
}

void graph::labelClick(bool status) {
    qDebug() << "Lable was set to " << status;
    this->update();
}

void graph::portfolioClick(bool status) {
    qDebug() << "Lable was set to " << status;
    this->update();
}

void graph::showStock(bool status) {
    qDebug() << "Lable was set to " << status;
    this->update();
}

void graph::showPortfolioData(bool status) {
    qDebug() << "Lable was set to " << status;
    this->update();
}

/*
** This is a slot which receives the changed string from the combo(dropdown) box and emits the signal stockValueChanged to the randomGenerator
** Next, it takes the chnaged combo box value and then updates that to get the stock data to plot and sets the stockDat variable;
** If the new stock that is shown in the combobox is not available in the cache, then it tries to declare a new on and populate the cache
** Last it updates the paintEvent to redraw the graph using the update() method
*/
void graph::myChoiceChanged(QString string) {

    emit stockValueChanged(string);
    QString filelocation;
    qDebug() << "Stock selected = " << string;

    stockCode = string;
    if (cache.contains(string)) {
        qDebug() << "Stock value exists in the cache" << string;
        stockData = cache.value(string);
        //Check if the prediction is complete for this stock, or else emit a signal so as to start the prediction
        if (stockData->predictorData.size() > 0) {
            QHash<QString, QString> lastLine;
            lastLine = stockData->predictorData.last();
            qDebug() << "Last value of the prediction = " << lastLine.value("time");
            QTime lastTimePredicted = QTime::fromString(lastLine.value("time"), "yyyy-MM-dd HH:mm:ss");
            if (lastTimePredicted <  QTime::fromString(MARKET_CLOSE, CLOCK_FORMAT)) {
                //start the prediction again.
                this->getPredictor()->setLastDataPredicted(lastLine);
            }
        }
    }
    else {
        if (string == googleStockCode) {
            filelocation = googleDataFile;
        }
        else if (string == appleStockCode) {
            filelocation = appleDataFile;
        }
        else if (string == microsoftStockCode) {
            filelocation = microsoftDataFile;
        }
        else {
            filelocation = googleDataFile;
        }
        Stock *s = new Stock(filelocation, string);
        cache.insert(string, s);
        this->getPredictor()->setLastDataPredicted(s->getLastLineRead());
        stockData = s;

    }

    int height = stockData->getHighestStockReading().toFloat() + 200;
    graph::setGeometry(100, 200, 1400, height);
    int timerValue = ui->spinBox->value();
    this->setPredictionTimer(QString(timerValue));
    if (stockData->predictorData.size() > 0) {
        QHash<QString, QString> lastLine = stockData->predictorData.at(stockData->predictorData.size()-1);
        this->getPredictor()->setLastDataPredicted(lastLine);
    }
    else {
        this->getPredictor()->setLastDataPredicted(stockData->getLastLineRead());
    }
    this->update();

}
/*
** This is the slot which receives the signal from the Predictor to redraw the graph with the prdictor values
** Have to modify this, if needed.
*/
void graph::predictionData(QHash<QString, QString> dataPredicted) {
    qDebug() << "Data received from Predictor = " << dataPredicted;
    stockData->addPredictorDataToStock(dataPredicted);
    if (dataPredicted.value("price").toFloat() > stockData->getHighestStockReading().toFloat()) {
        stockData->setHighestStockReading(dataPredicted.value("price"));
        int height = stockData->getHighestStockReading().toFloat()+ 200;
        graph::setGeometry(100, 200, 1400, height);
    }
    this->update();
}

/*
** This function is a setter function to set the stockData that should be drawn on the widget
*/
void graph::setStockData(Stock *stock) {
    stockData = stock;
    int height = stockData->getHighestStockReading().toFloat()+ 200;
    graph::setGeometry(100, 200, 1400, height);
}

/*
** This function is called before the closing the widget.
** So now write the predicted data to the outfile.
*/
void graph::closeEvent(QCloseEvent *) {
    qDebug() << "in CloseEvent";
}

void graph::setPredictor(Predictor *p) {
    predictor = p;
}

/*
** This is the slot which receives the number from the spinner
** When the value changes of hte spinner, update the frequency of change
*/
void graph::spinnerBoxValueChanged(QString number) {
//    qDebug() << "SPINNER VALUE = " << number;
    this->setPredictionTimer(number);
    this->startPredictionTimer();
}

void graph::spinnerBoxGoogleChanged(int value) {
    qDebug() << "Value of google stock = " << value;
    this->update();
}

void graph::spinnerBoxAppleChanged(int value) {
    qDebug() << "Value of apple stock = " << value;
    this->update();
}

void graph::spinnerBoxMicrosoftChanged(int value) {
    qDebug() << "Value of microsoft stock = " << value;
    this->update();
}

void graph::startPredictionTimer() {

    //this->getPredictor()->setLastDataPredicted(stockData->getLastLineRead());
    QString predictionTimerData = this->getPredictionTimer().split(" ")[0];
    qDebug() << "Timer has been update to = "<< predictionTimerData;
    int mSec  = predictionTimerData.toFloat() * 1000;
//    qDebug() << "Timer value = " << mSec;
    timer->start(mSec);
}

Predictor* graph::getPredictor() {
    return predictor;
}

void graph::setPredictionTimer(QString number) {
    predictionTimer = number;
}

QString graph::getPredictionTimer() {
    return predictionTimer;
}

